/* JavaLog - package tracker 
 * 
 * Copyright (C) 2009 Allyson Lima, Diego Pedro, Victor Freire
 * 
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software 
 * Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with this 
 * program. If not, see <http://www.gnu.org/licenses/>.
 */

package br.edu.ufcg.ccc.javalog.model;

import java.text.NumberFormat;
import java.util.Locale;

import br.edu.ufcg.ccc.javalog.exceptions.LocationException;
import br.edu.ufcg.ccc.javalog.model.StringUtil;
import br.edu.ufcg.ccc.javalog.model.Validator;

/**
 * A Location
 * 
 * @author Allyson Lima, Diego Pedro, Victor Freire
 * @version 08/11/09
 */
public class Location {

	/**
	 * Earth radius
	 */
	private static final double EARTH_RADIUS = 6.371;
	/**
	 * Earth circumference
	 */
	private static final double EARTH_CIRCUMFERENCE = 2 * Math.PI
			* EARTH_RADIUS;
	/**
	 * Latitude coordinate city
	 */
	private double latitude;
	/**
	 * Longitude coordinate city
	 */
	private double longitude;

	/**
	 * Constructor default
	 * 
	 * @param longitude
	 *            Longitude of city
	 * @param latitude
	 *            Latitude of city
	 * @throws LocationException
	 *             Exception for location
	 */
	public Location(String longitude, String latitude) throws LocationException {
		Validator.validateString(longitude, new LocationException(
				"Logitude can not be null"));

		Validator.validateString(latitude, new LocationException(
				"Latitude can not be null"));

		Validator.validateGeographicalPositionFormat(latitude,
				new LocationException("Format invalid to latitude"));

		Validator.validateGeographicalPositionFormat(longitude,
				new LocationException("Format invalid to longitude"));

		if (StringUtil.containsInvalidChar(latitude, "0123456789-:") != null)
			throw new LocationException("'"
					+ StringUtil.containsInvalidChar(latitude, "0123456789-:")
					+ "' is a Character invalid to latitude");

		if (StringUtil.containsInvalidChar(longitude, "0123456789-:") != null)
			throw new LocationException("'"
					+ StringUtil.containsInvalidChar(longitude, "0123456789-:")
					+ "' is a Character invalid to longitude");

		Validator.validateGeographicalPositionLimit(latitude,
				new LocationException("invalid limit latitude"));

		Validator.validateGeographicalPositionLimit(longitude,
				new LocationException("invalid limit longitude"));

		this.latitude = parseDegreesInSeconds(latitude);
		this.longitude = parseDegreesInSeconds(longitude);
	}

	/**
	 * Return parse degrees in seconds.
	 * 
	 * @param degrees
	 *            Degrees of latitude or longitude
	 * @return parse degrees in seconds
	 * @throws DistanceException
	 *             Except for distances
	 */
	private double parseDegreesInSeconds(String degrees)
			throws LocationException {
		String latSplit[] = degrees.split(":");
		double hour = Double.parseDouble(latSplit[0]);
		double minutes = Double.parseDouble(latSplit[1]);
		double seconds = Double.parseDouble(latSplit[2]);

		// if degree is negative
		if (hour < 0.0)
			return -((-hour * 3600) + (minutes * 60) + seconds) / (3600);
		return ((hour * 3600) + (minutes * 60) + seconds) / (3600);
	}

	/*
	 * Point A => refers to a point in the north pole passing through the
	 * Greenwich meridian 
	 * POINT B => point in the meridian passing through the
	 * city 
	 * POINT C => point in the meridian passing through the city B
	 */

	/**
	 * Difference between the longitudes of two cities ARC BC.
	 * 
	 * @return the arc A
	 */
	private double getArcBC(double longitudeCityA, double longitudeCityB) {
		return (longitudeCityA - longitudeCityB);
	}

	/**
	 * ARC AC
	 * 
	 * @param latitude
	 *            City A
	 * @return the arc C
	 */
	private double getArcAB(double latitude) {
		return (90 - latitude);
	}

	/**
	 * ARCO AB
	 * 
	 * @param latitude
	 *            City B
	 * @return o arcoB
	 */
	private double getArcAC(double latitude) {
		return (90 - latitude);
	}

	/**
	 * 
	 * Returns the distance between two cities in degrees.
	 * 
	 * @param location
	 *            location of the other city
	 * @return the distance between two cities in degrees
	 */
	public double calculateDistance(Location location) throws LocationException {

		Validator.validateObject(location, new LocationException(
				"coordinates can not be null"));

		// cos(a) = cos(b) . cos(c) + sen(b) . sen(c) . cos(a)
		double ArcA = Math.toRadians(getArcBC(this.longitude,
				location.longitude));
		double ArcB = Math.toRadians(getArcAC(location.latitude));
		double ArcC = Math.toRadians(getArcAB(this.latitude));
		double cosA = (Math.cos(ArcB) * Math.cos(ArcC))
				+ (Math.sin(ArcB) * Math.sin(ArcC) * Math.cos(ArcA));
		double distanceInDegress = (Math.acos(cosA) * 180) / Math.PI;

		return ((EARTH_CIRCUMFERENCE * distanceInDegress) / 360.0) * 1000;

	}

	/**
	 * Returns latitude of city.
	 * 
	 * @return latitude of city
	 */
	public double getLatitude() {
		return latitude;
	}

	/**
	 * Modifies the latitude of city.
	 * 
	 * @param latitude
	 *            Latitude of city
	 */
	public void setLatitude(double latitude) {
		this.latitude = latitude;
	}

	/**
	 * Returns longitude of city.
	 * 
	 * @return longitude of city
	 */
	public double getLongitude() {
		return longitude;
	}

	/**
	 * Modifies the longitude of city.
	 * 
	 * @param longitude
	 *            Longitude of city
	 */
	public void setLongitudeC(double longitude) {
		this.longitude = longitude;
	}

	/**
	 * Describes the location
	 * 
	 * @return description of the location
	 */
	@Override
	public String toString() {
		String stringLatitude = NumberFormat.getInstance(Locale.ENGLISH)
				.format(getLatitude());
		String stringLongitude = NumberFormat.getInstance(Locale.ENGLISH)
				.format(getLongitude());
		return "Latitude: " + stringLatitude + "\n" + "Longitude: "
				+ stringLongitude;
	}

	/**
	 * Compares two locations.
	 * 
	 * @return true if both location are equal
	 */
	@Override
	public boolean equals(Object obj) {
		if (!(obj instanceof Location)) {
			return false;
		}
		return (this.latitude == ((Location) obj).getLatitude())
				&& (this.longitude == ((Location) obj).getLongitude());
	}

	/**
	 * HashCode method.
	 * 
	 * @return HashCode
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		long temp;
		temp = Double.doubleToLongBits(latitude);
		result = prime * result + (int) (temp ^ (temp >>> 32));
		temp = Double.doubleToLongBits(longitude);
		result = prime * result + (int) (temp ^ (temp >>> 32));
		return result;
	}

}
